use std::collections::BTreeMap;

use derive_where::derive_where;
use indoc::indoc;
use serde::{Deserialize, Serialize};

use crate::flavor::MoveFlavor;

use super::{
    EnvironmentID, EnvironmentName, PublishAddresses, RenderToml,
    toml_format::{expand_toml, flatten_toml},
};

/// The schema for a `Move.published` file
#[derive(Serialize, Deserialize, Debug)]
#[serde(bound = "")]
#[derive_where(Default, Clone)]
pub struct ParsedPublishedFile<F: MoveFlavor> {
    #[serde(default)]
    pub published: BTreeMap<EnvironmentName, Publication<F>>,
}

/// A `Publication` is a historical record describing the state of a package when it was published.
/// It contains
#[derive(Debug, Serialize, Deserialize)]
#[derive_where(Clone)]
#[serde(rename_all = "kebab-case")]
pub struct Publication<F: MoveFlavor> {
    pub chain_id: EnvironmentID,

    #[serde(flatten)]
    pub addresses: PublishAddresses,

    pub version: u64,

    /// Additional flavor-specific fields, such as an upgrade capability or information for source
    /// verification
    #[serde(flatten)]
    pub metadata: F::PublishedMetadata,
}

impl<F: MoveFlavor> RenderToml for ParsedPublishedFile<F> {
    /// Pretty-print `self` as TOML
    fn render_as_toml(&self) -> String {
        let mut toml = toml_edit::ser::to_document(self).expect("toml serialization succeeds");
        expand_toml(&mut toml);

        for (_, chain) in toml["published"].as_table_like_mut().unwrap().iter_mut() {
            flatten_toml(chain);
        }

        toml.decor_mut().set_prefix(indoc!(
            r#"
            # Generated by Move
            # This file contains metadata about published versions of this package in different environments
            # This file SHOULD be committed to source control

            "#
        ));

        toml.to_string()
    }
}

#[cfg(test)]
mod tests {
    use indoc::indoc;
    use pretty_assertions::assert_eq;
    use test_log::test;

    use crate::{flavor::Vanilla, schema::RenderToml};

    use super::ParsedPublishedFile;

    /// Parsing and rendering a pubfile produces the original input
    #[test]
    fn parse_render_pubfile() {
        let original = indoc!(
            r###"
            # Generated by Move
            # This file contains metadata about published versions of this package in different environments
            # This file SHOULD be committed to source control

            [published.mainnet]
            chain-id = "mainnet chain ID"
            published-at = "0x000000000000000000000000000000000000000000000000000000000000cccc"
            original-id = "0x000000000000000000000000000000000000000000000000000000000000cc00"
            version = 1
            build-config = { edition = "2024", flavor = "vanilla" }

            [published.testnet]
            chain-id = "testnet chain id"
            published-at = "0x0000000000000000000000000000000000000000000000000000000000001234"
            original-id = "0x0000000000000000000000000000000000000000000000000000000000005678"
            version = 1
            build-config = { edition = "2024", flavor = "vanilla" }
            "###
        );

        let parsed: ParsedPublishedFile<Vanilla> = toml_edit::de::from_str(original).unwrap();
        let rendered = parsed.render_as_toml();
        assert_eq!(rendered, original);
    }
}
